排程讓 agent 的動作從有人在場變成時間到就跑,出事時同樣沒有人在場,事後能看的只剩排程自己留下的紀錄。這一輪讀 hermes cron 各子指令留下什麼,量這台機器上五週的執行紀錄,再做一個完全不經過模型的週期檢查任務。
| 指令 | 回答的問題 | 資料位置 |
|---|---|---|
cron list |
每個 job 的排程、下次執行時間、上次結果 | cron/jobs.json |
cron runs |
每一次執行嘗試的狀態與錯誤訊息 | cron/executions.db |
cron incidents |
失敗事件的生命週期,狀態為 detected、alerted、closed |
指令輸出,附對應的 output 檔路徑 |
cron doctor |
job 的健康問題,例如下次執行時間已逾期 | 讀上列資料 |
cron status |
排程器有沒有在跑 | cron/ticker_heartbeat |
| 每次執行的輸出 | 當次的 prompt 與回應全文 | cron/output/<job_id>/<時間>.md |
執行嘗試以持久紀錄保存,連終態寫不進去的那種也記成 unknown。
cron runs --limit 500 列出 2026-07-31 到 2026-09-06 之間的全部嘗試,共 189 筆:
| job | completed | failed | unknown |
|---|---|---|---|
door-fans-off-1100 |
7 | 6 | 0 |
door-fans-on-1105 |
9 | 5 | 0 |
env-status-telegram |
150 | 10 | 2 |
| 合計 | 166 | 21 | 2 |
兩個定時開關風扇的 job 共 27 次,成功 16 次。jobs.json 裡的 repeat.completed 是 13 與 14,等於成功加失敗的次數(7+6、9+5),這個欄位計的是嘗試次數。
23 筆未完成的紀錄,錯誤訊息分成五種:
| 錯誤訊息 | 筆數 |
|---|---|
RuntimeError: Request timed out. |
13 |
TimeoutError: Cron job ... idle for ...s (limit 600s) |
4 |
| 模型的 context window 32,768 低於 Hermes 要求的 64,000 | 2 |
Context length exceeded,壓縮後仍超出 |
2 |
Scheduler restarted after this execution's owner exited ... |
2 |
最後一種是 unknown 狀態的原文:
Scheduler restarted after this execution's owner exited before a durable terminal state; whether side effects ran is unknown
這句話對會操作設備的 job 有具體意義。執行到一半行程消失,排程器重啟後只知道這次嘗試沒有終態,風扇有沒有動得去讀感測端點才知道。
兩個風扇 job 最後一次成功是 2026-08-12,之後的四次嘗試全部失敗,cron list 顯示 4 failures in a row,依序是 Context length exceeded、context 低於下限、請求逾時、請求逾時。
2026-10-09 12:47 的 cron status:
⚠ Gateway is running but the cron ticker looks STALLED — no heartbeat for 2838959s (expected every ~60s).
PID: 2512
Cron jobs may NOT be firing. Restart: hermes gateway restart
同一時間 cron doctor 對兩個風扇 job 各列出兩個問題,其中一個是:
next_run_at is 769.8h overdue — job is not firing (is the scheduler running?)
當下量到的狀態:
PID 2512 是一個執行中的 hermes_cli.main gateway run,啟動時間 2026-10-09 12:47:20ticker_heartbeat 的內容停在 2026-09-06 16:11:52,2,838,959 秒約 32.9 天jobs.json 與 executions.db 的最後寫入是 2026-09-06 11:07:59
gateway_state.json 記的 PID 是 11304,與現在這個行程不同,寫入時間同樣是 2026-09-06logs/gateway.log 最後一行是 2026-09-06 13:11
行程存活與排程器在計時是兩個獨立的訊號。cron status 的警告句同時寫出 Gateway is running 與 ticker looks STALLED,兩句各自都是事實,成因無法從這些檔案判定。
Gateway 行程存在,不代表排程器在計時,兩個訊號要分開讀
hermes cron create 有 --no-agent 與 --script:腳本就是任務本身,stdout 原樣投遞,空輸出代表靜默。以下是一個週期自我檢查,每次做兩件事:
hermes security audit --json --fail-on high,把命中的 套件|漏洞編號 與基準比對hermes prompt-size --json,把工具 schema 的 byte 數與基準比對判定失敗的條件是出現新的 HIGH 以上漏洞,或工具 schema 比基準大 10% 以上。腳本節錄:
env = {k: v for k, v in os.environ.items() if k != "HERMES_HOME"}
audit, audit_rc = hermes_json("security", "audit", "--json", "--fail-on", "high")
size, _ = hermes_json("prompt-size", "--json")
new_gated = {k: v for k, v in new.items() if v in {"HIGH", "CRITICAL"}}
growth = now["tools_bytes"] / base["tools_bytes"] - 1
fail = bool(new_gated) or growth > 0.10
lines = [f"# 自我檢查 {datetime.now().astimezone().isoformat(timespec='seconds')}"]
print("\n".join(lines))
sys.exit(1 if fail else 0)
第一行過濾 HERMES_HOME 有原因。排程在獨立的 HERMES_HOME 下跑時,腳本裡呼叫的 hermes -p default 仍然讀到那個目錄,config path 實測指向的是獨立目錄,不是預設 profile 的組態。
建 job 與兩次執行的結果:
hermes cron create --name selfaudit --no-agent --script selfaudit.py \
--deliver local "0 3 * * *"
| 腳本印出的時間 | 條件 | 退出碼 | cron runs |
incident |
|---|---|---|---|---|
| 12:51:06 | 無基準檔 | 0 | completed | 無 |
| 12:51:48 | 基準檔刪兩筆漏洞、schema 基準改成 33,000 byte | 1 | failed | detected,Type 為 script |
第二次的失敗條件是人為製造的,用來走到失敗路徑。輸出如下:
# 自我檢查 2026-10-09T12:51:48+08:00
> 排程 0 3 * * *|元件 130|命中 60
- 新增漏洞 2 筆,其中 HIGH 以上 1 筆
- 工具 schema 37012 byte,相對基準 +12.2%
- 判定 FAIL
失敗時 stdout 整段進了 cron runs 的錯誤欄、incident 的 Error 欄位(顯示時截斷)與當次的 output 檔。投遞這一側,--failure-deliver 可以把失敗通知導向別的目標,說明寫的是 local 會完全抑制失敗通知,狀態仍可從 cron list 讀到,省略時失敗跟著 --deliver 走。
基準檔建立那次也順便量到漏洞的變化。前一輪在 10-04 量到 57 筆,同一組 130 個元件在 10-09 是 60 筆:
| 嚴重度 | 10-04 | 10-09 |
|---|---|---|
| CRITICAL | 2 | 2 |
| HIGH | 12 | 12 |
| MODERATE | 16 | 18 |
| UNKNOWN | 27 | 28 |
筆數增加的三筆來自 MODERATE(+2)與 UNKNOWN(+1),--fail-on high 的閘門維持原狀。新增的那筆 UNKNOWN 在任何門檻下都不會觸發閘門。
這個任務跑在沒有任何模型組態的 HERMES_HOME 下,由 CLI 的 cron run 與 cron tick 當下執行,兩次都照常完成,執行路徑上沒有推論端點。
風扇 job 的第一個版本在 2026-08-05 12:24:46 手動觸發,排程是 0 11 * * *。那次的 output 檔記錄了 prompt 與回應:
**Run Time:** 2026-08-05 12:24:46
**Schedule:** 0 11 * * *
工具回傳 ok=true 時,最終訊息只輸出這一行:
已於 11:00 關閉前後門風扇(4 顆全部 OFF)
Response:
已於 11:00 關閉前後門風扇(4 顆全部 OFF)
回應與 prompt 範本裡的那一行逐字相同。「11:00」是 prompt 範本裡的字面內容,模型照抄,沒有生成任何時間。 排程準時觸發的執行落在 11:00 前後,字樣大致吻合,手動觸發時才與實際時間分開。
cron 在 prompt 前面加的前置段(You are running as a scheduled cron job. DELIVERY: ...)全文沒有日期或時間。現行兩個 job 的副標只寫 定時排程|每天 11:00,那是排程的定義,每次執行都為真。
要讓通知帶「實際執行時間」,來源得是讀系統時鐘的程式,上一節的腳本就是這樣做的,datetime.now() 每次印出的值不同。
通知裡的時間字樣取決於它從哪裡來,範本寫死的值在每一次執行都原樣出現
| 概念 | 來源 | 年份 |
|---|---|---|
cron 子指令清單、create 的旗標與 --failure-deliver 語意 |
hermes cron --help、hermes cron create --help,Hermes Agent v0.21.0 |
2026 |
| 189 筆執行紀錄的分布與五種錯誤訊息 | 本機實測,hermes cron runs --limit 500,2026-10-09 |
2026 |
STALLED 訊息、769.8h overdue、心跳與狀態檔的時間 |
本機實測,hermes cron status、hermes cron doctor 與 cron/ 目錄檔案時間,2026-10-09 |
2026 |
| 無模型任務的兩次執行與 incident | 本機實測,獨立 HERMES_HOME 下的 hermes cron create --no-agent,2026-10-09 |
2026 |
| 漏洞數量變化 | 本機實測,hermes security audit --json,2026-10-09,對照 2026-10-04 的量測 |
2026 |
| 手動觸發的輸出紀錄 | 本機 cron/output/ 下 2026-08-05 的執行紀錄 |
2026 |
要試跑一個排程任務,第一個想到的指令是 cron tick。--help 對它的說明是「Run due jobs once and exit」,而 cron list 上兩個風扇 job 的下次執行時間是 2026-09-07 11:00,已經過期,到期的 job 會全部被執行一次,其中兩個會去操作真實的風扇。
試跑改在一個獨立的 HERMES_HOME 裡進行,那裡只有新建的那一個 job。tick 與 run 都只動得到那個目錄的 jobs.json。
這個做法本身帶出另一件事:同一台機器上,排程的狀態、紀錄與心跳全部以檔案存在 cron/ 底下,換一個 HERMES_HOME 就是另一個排程器。查排程有沒有跑,要先確認讀的是哪一個目錄。
試跑排程任務之前,先確認這個指令會一併觸發哪些已經到期的 job
AMA-Bench、MemoryArena 這類評測基準量的是什麼,以及自己的驗收條件怎麼寫成機器判讀。